﻿//===================================================================================
// Copyright (c) Ryan Garrett.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
using System;
using System.Collections.Generic;
using System.Linq;

namespace Caffeine.Core
{
    /// <summary>
    /// Provides a default property change counter implementation.
    /// </summary>
    public class PropertyChangeCounter : IPropertyChangeCounter
    {
        #region State

        private readonly List<WeakReference> _internalObservers;

        #endregion

        #region Public

        /// <summary>
        /// Constructor
        /// </summary>
        public PropertyChangeCounter()
        {
            _internalObservers = new List<WeakReference>();
        }

        /// <summary>
        /// Gets the number of changes that have been made to a model or model graph.
        /// </summary>
        public virtual UInt64 ChangeCount
        {
            get;
            private set;
        }

        /// <summary>
        /// Increments the ChangeCount property by 1. Model properties will call this method every time their value changes
        /// if their parent model's PropertyChangeCounter property is set.
        /// </summary>
        /// <param name="property">Model property which has changed</param>
        public virtual void IncrementChangeCount(ModelPropertyBase property)
        {
            ChangeCount++;

            foreach (Action<UInt64, ModelPropertyBase> observer in Observers)
            {
                observer(ChangeCount, property);
            }
        }

        /// <summary>
        /// Returns a list of registered observers.
        /// </summary>
        public IEnumerable<Action<UInt64, ModelPropertyBase>> Observers
        {
            get
            {
                return GetObservers();
            }
        }

        /// <summary>
        /// Add an observer of property change notifications. This implementation of IPropertyChangeCounter holds weak
        /// references to the delegates passed in.
        /// </summary>
        /// <param name="observerMethod">Method to call when a property is changed</param>
        public void AddObserver(Action<UInt64, ModelPropertyBase> observerMethod)
        {
            if (!Observers.Contains(observerMethod))
            {
                InternalObservers.Add(new WeakReference(observerMethod));
            }
        }

        /// <summary>
        /// Remove an observer of property change notifications. 
        /// </summary>
        /// <param name="observerMethod">Method to call when a property is changed</param>
        public void RemoveObserver(Action<UInt64, ModelPropertyBase> observerMethod)
        {
            WeakReference observerReferenceToRemove = null;

            foreach (WeakReference methodReference in InternalObservers)
            {
                if ((methodReference.Target as Action<UInt64, ModelPropertyBase>) == observerMethod)
                {
                    observerReferenceToRemove = methodReference;
                    break;
                }
            }

            if (observerReferenceToRemove != null)
            {
                InternalObservers.Remove(observerReferenceToRemove);
            }
        }

        #endregion

        #region Private

        private List<WeakReference> InternalObservers
        {
            get
            {
                return _internalObservers;
            }
        }

        private IEnumerable<Action<UInt64, ModelPropertyBase>> GetObservers()
        {
            List<WeakReference> referencesToRemove = null;

            foreach (WeakReference reference in InternalObservers)
            {
                Action<UInt64, ModelPropertyBase> target = reference.Target as Action<UInt64, ModelPropertyBase>;

                if (target != null)
                {
                    yield return target;
                }
                else
                {
                    referencesToRemove = referencesToRemove ?? new List<WeakReference>();
                    referencesToRemove.Add(reference);
                }
            }

            if (referencesToRemove != null)
            {
                foreach (WeakReference reference in referencesToRemove)
                {
                    InternalObservers.Remove(reference);
                }
            }
        }

        #endregion
    }
}
